package com.github.cakin.shiro.spring;

import org.apache.shiro.config.Ini;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.util.Nameable;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.NamedFilterList;
import org.apache.shiro.web.filter.mgt.SimpleNamedFilterList;

import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @className: CustomDefaultFilterChainManager
 * @description: 扩展 DefaultFilterChainManager
 * @date: 2020/5/28
 * @author: cakin
 */
public class CustomDefaultFilterChainManager extends DefaultFilterChainManager {
    // 可以认为是默认的静态拦截器链；会自动与数据库中加载的合并；来自
    // <property name="defaultFilterChainDefinitions">
    // <value>
    // /login = authc
    // /logout = logout
    // /unauthorized.jsp = authc
    // /** = user,sysUser
    // </value>
    // </property>
    /**
     * 用于存储配置文件中配置的拦截器链定义
     */
    private Map<String, String> filterChainDefinitionMap = null;
    /**
     * 对应登录地址,用于给相应拦截器的
     */
    private String loginUrl;
    /**
     * 登录成功后默认跳转地址，用于给相应拦截器的
     */
    private String successUrl;
    /**
     * 未授权跳转地址，用于给相应拦截器的
     */
    private String unauthorizedUrl;

    /**
     * 功能描述：调用其构造器时，会自动注册默认的拦截器
     *
     * @author cakin
     * @date 2020/5/28
     */
    public CustomDefaultFilterChainManager() {
        setFilters(new LinkedHashMap<String, Filter>());
        setFilterChains(new LinkedHashMap<String, NamedFilterList>());
        // 自动注册默认的拦截器到 filters
        addDefaultFilters(false);
    }

    public Map<String, String> getFilterChainDefinitionMap() {
        return filterChainDefinitionMap;
    }

    public void setFilterChainDefinitionMap( Map<String, String> filterChainDefinitionMap ) {
        this.filterChainDefinitionMap = filterChainDefinitionMap;
    }

    /**
     * 功能描述：注册我们自定义的拦截器；如 ShiroFilterFactoryBean 的 filters 属性
     *
     * @param customFilters 自定义过滤器
     * @author cakin
     * @date 2020/5/28
     * @description: 对应配置文件下面内容的注入：
     */
    // <property name="customFilters">
    // <util:map>
    // <entry key="authc" value-ref="formAuthenticationFilter"/>
    // <entry key="sysUser" value-ref="sysUserFilter"/>
    // </util:map>
    // </property>
    public void setCustomFilters( Map<String, Filter> customFilters ) {
        for (Map.Entry<String, Filter> entry : customFilters.entrySet()) {
            addFilter(entry.getKey(), entry.getValue(), false);
        }
    }

    /**
     * 功能描述：解析配置文件中传入的字符串拦截器链配置，解析为相应的拦截器链；
     *
     * @param definitions 过滤器链
     * @return
     * @author cakin
     * @date 2020/5/28
     * @description: 解析下面这段
     */
    // <property name="defaultFilterChainDefinitions"
    // <value>
    // /login = authc
    // /logout = logout
    // /unauthorized.jsp = authc
    // /** = user,sysUser
    // </value>
    // </property>
    public void setDefaultFilterChainDefinitions( String definitions ) {
        Ini ini = new Ini();
        ini.load(definitions);
        //did they explicitly state a 'urls' section?  Not necessary, but just in case:
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
        if (CollectionUtils.isEmpty(section)) {
            //no urls section.  Since this _is_ a urls chain definition property, just assume the
            //default section contains only the definitions:
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        setFilterChainDefinitionMap(section);
    }

    public String getLoginUrl() {
        return loginUrl;
    }

    public void setLoginUrl( String loginUrl ) {
        this.loginUrl = loginUrl;
    }

    public String getSuccessUrl() {
        return successUrl;
    }

    public void setSuccessUrl( String successUrl ) {
        this.successUrl = successUrl;
    }

    public String getUnauthorizedUrl() {
        return unauthorizedUrl;
    }

    public void setUnauthorizedUrl( String unauthorizedUrl ) {
        this.unauthorizedUrl = unauthorizedUrl;
    }

    /**
     * 功能描述：初始化方法，Spring 容器启动时会调用，
     * 首先其会自动给相应的拦截器设置如 loginUrl、successUrl、unauthorizedUrl；
     * 其次根据 filterChainDefinitionMap 构建默认的拦截器链
     *
     * @author cakin
     * @date 2020/5/28
     */
    @PostConstruct
    public void init() {
        //Apply the acquired and/or configured filters:
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                // 给相应的拦截器设置如 loginUrl、successUrl、unauthorizedUrl；
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
            }
        }

        // 根据filterChainDefinitionMap 构建默认的拦截器链
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                createChain(url, chainDefinition);
            }
        }
    }

    @Override
    protected void initFilter( Filter filter ) {
        //ignore
    }

    /**
     * 功能描述：组合多个拦截器链为一个生成一个新的 FilterChain 代理
     *
     * @param original   原始过滤器
     * @param chainNames 过滤器链列表的名称
     * @return FilterChain 过滤器链
     * @author cakin
     * @date 2020/5/28
     */
    public FilterChain proxy( FilterChain original, List<String> chainNames ) {
        NamedFilterList configured = new SimpleNamedFilterList(chainNames.toString());
        for (String chainName : chainNames) {
            configured.addAll(getChain(chainName));
        }
        return configured.proxy(original);
    }

    private void applyLoginUrlIfNecessary( Filter filter ) {
        String loginTmpUrl = getLoginUrl();
        if (StringUtils.hasText(loginTmpUrl) && (filter instanceof AccessControlFilter)) {
            AccessControlFilter acFilter = (AccessControlFilter) filter;
            //only apply the login url if they haven't explicitly configured one already:
            String existingLoginUrl = acFilter.getLoginUrl();
            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
                acFilter.setLoginUrl(loginTmpUrl);
            }
        }
    }

    private void applySuccessUrlIfNecessary( Filter filter ) {
        String successTmpUrl = getSuccessUrl();
        if (StringUtils.hasText(successTmpUrl) && (filter instanceof AuthenticationFilter)) {
            AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
            //only apply the successUrl if they haven't explicitly configured one already:
            String existingSuccessUrl = authcFilter.getSuccessUrl();
            if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
                authcFilter.setSuccessUrl(successTmpUrl);
            }
        }
    }

    private void applyUnauthorizedUrlIfNecessary( Filter filter ) {
        String unauthorizedTmpUrl = getUnauthorizedUrl();
        if (StringUtils.hasText(unauthorizedTmpUrl) && (filter instanceof AuthorizationFilter)) {
            AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
            //only apply the unauthorizedUrl if they haven't explicitly configured one already:
            String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
            if (existingUnauthorizedUrl == null) {
                authzFilter.setUnauthorizedUrl(unauthorizedTmpUrl);
            }
        }
    }

    /**
     * 功能描述：给相应的拦截器设置如 loginUrl、successUrl、unauthorizedUrl；
     *
     * @param filter 拦截器
     * @author cakin
     * @date 2020/5/28
     */
    private void applyGlobalPropertiesIfNecessary( Filter filter ) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }
}
